﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
using System.Xml;
using System.Reflection;
using System.Windows.Forms;
using System.Security;
using System.Threading;
using System.Windows.Threading;

namespace XNACompilator
{
    /// <summary>
    /// Class use for build data easily
    /// </summary>
    public class Builder
    {
        #region Field

        private readonly BuildParameter m_parameter;
        private readonly ContentBuilder m_builder;

        private List<string> m_assemblieLoad;

        private Dispatcher m_dispatcher;
        private bool m_async = false;

        #endregion

        #region Property
        #endregion

        #region Event

        //public event Action<CompileException, Asset> BuildEnded;

        #endregion

        #region CTOR

        /// <summary>
        /// Constructor
        /// </summary>
        public Builder()
            : this(new BuildParameter())
        {
        }

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="parameter">Parameter use for specifie outputPath, compile plateform ...</param>
        public Builder(BuildParameter parameter)
        {
            m_parameter = parameter;
            m_builder = new ContentBuilder(m_parameter.OutPut, m_parameter.PlateformType);
            m_assemblieLoad = new List<string>();
            m_dispatcher = Dispatcher.CurrentDispatcher;
        }

        #endregion

        #region Method

        public void Clear()
        {
            m_builder.Clear();
        }

        public void AsyncBuild(Asset asset, bool _override = false, string subDirectorie = "", Action<CompileException, Asset> _result = null)
        {
            m_async = true;
            Thread t = new Thread(() =>
            {
                CompileException _exception = null;
                try {
                Build(asset, _override, subDirectorie);
                }
                catch (CompileException e) { _exception = e; }
                if (_result != null)
                    m_dispatcher.Invoke(_result, _exception, asset);
            });
            t.Priority = ThreadPriority.Highest;
            t.Start();
        }

        /// <summary>
        /// Method use to create an XNB from an asset information
        /// </summary>
        /// <param name="asset">Asset specifie all parameter to compile</param>
        /// <param name="_override">specifie if it's nessecary to override the file in the output path if it's already exist</param>
        /// <param name="subDirectorie">Specifie an subdirectory</param>
        /// <exception cref="CompileException">Throw an CompileException if the compilation did'nt works</exception>
        public void Build(Asset asset, bool _override = false, string subDirectorie = "")
        {
            m_builder.Add(asset);
            string t = CompletBuild(_override, subDirectorie);
            var _compileException = (string.IsNullOrEmpty(t) ? null : new CompileException(t, asset));
            if (!string.IsNullOrEmpty(t))
                throw _compileException;
        }

        /// <summary>
        /// Method use to create an XNB from an asset information
        /// </summary>
        /// <param name="assets">IEnumerable asset specifie all parameter to compile</param>
        /// <param name="_override">specifie if it's nessecary to override the file in the output path if it's already exist</param>
        /// <param name="subDirectorie">Specifie an subdirectory</param>
        /// <exception cref="CompileException">Throw an CompileException if the compilation did'nt works</exception>
        public void Build(IEnumerable<Asset> assets, bool _override = false, string subDirectorie = "")
        {
            foreach (var elem in assets)
                m_builder.Add(elem);
            string t = CompletBuild(_override, subDirectorie);
            if (!string.IsNullOrEmpty(t))
                throw new CompileException(t, assets);
        }


        public string SaveToXml<T>(T obj, string _name, bool _override = true, bool copy = false, string extention = ".xml")
        {
            string filePath = Path.Combine(m_builder.OutputDirectory, _name);
            filePath = Path.ChangeExtension(filePath, extention);
            if (!Directory.Exists(m_builder.OutputDirectory))
                Directory.CreateDirectory(m_builder.OutputDirectory);
            if (!Directory.Exists(m_parameter.OutPut))
                Directory.CreateDirectory(m_parameter.OutPut);
            if (File.Exists(filePath) && copy)
                File.Delete(filePath);
            using (FileStream stream = File.Create(filePath))
            {
                using (XmlWriter writer = XmlWriter.Create(stream, new XmlWriterSettings() { Indent = true }))
                {
                    IntermediateSerializer.Serialize(writer, obj, _name);
                }
            }
            string _outputFile = Path.Combine(m_parameter.OutPut, Path.GetFileName(filePath));
            if (copy)
                File.Copy(filePath, _outputFile, _override);

            return _outputFile;
        }

        /// <summary>
        /// Method use to tranform any class into an XNB by dint of IntermediateSerializer
        /// </summary>
        /// <typeparam name="T">Type of the class</typeparam>
        /// <param name="obj">Object to serialize</param>
        /// <param name="name">This parameter will be use for name without extention the xnb file</param>
        /// <param name="_override">specifie if it's nessecary to override the file in the output path if it's already exist</param>
        /// <param name="subDirectorie">Specifie an subdirectory</param>
        /// <exception cref="CompileException">Throw an CompileException if the compilation did'nt works</exception>
        public void SaveObjectToXNB<T>(T obj, string name = null, bool _override = false, string subDirectorie = "")
        {
            if (name == null)
                name = obj.ToString();
            name += ".xml";
            Assembly _as = Assembly.GetAssembly(typeof(T));
            if (!m_assemblieLoad.Contains(_as.FullName))
            {
                m_assemblieLoad.Add(_as.FullName);
                m_builder.AddReference(_as.EscapedCodeBase.Replace("file:///", ""));
            }
            _as = Assembly.GetEntryAssembly();
            if (!m_assemblieLoad.Contains(_as.FullName))
            {
                m_assemblieLoad.Add(_as.FullName);
                m_builder.AddReference(_as.EscapedCodeBase.Replace("file:///", ""));
            }
            string filePath = SaveToXml<T>(obj, name, _override);
            //string filePath = Path.Combine(m_builder.OutputDirectory, name);
            //if (!Directory.Exists(m_builder.OutputDirectory))
            //    Directory.CreateDirectory(m_builder.OutputDirectory);
            //using (FileStream stream = File.Create(filePath))
            //{
            //    using (XmlWriter writer = XmlWriter.Create(stream, new XmlWriterSettings() { Indent = true }))
            //    {
            //         IntermediateSerializer.Serialize(writer, obj, name);
            //    }
            //}
            Asset asset = new Asset();
            asset.Name = Path.GetFileNameWithoutExtension(filePath);
            asset.Importer = new Processor { Name = "XmlImporter" };
            asset.Exporter = null;
            asset.FileName = filePath;

            Build(asset, _override, subDirectorie);
        }

        public static T Load<T>(string path)
        {
            using (FileStream _stream = File.Open(path, FileMode.Open))
            {
                using (XmlReader _reader = XmlReader.Create(_stream))
                {
                    return IntermediateSerializer.Deserialize<T>(_reader, path);
                }
            }
        }

        /// <summary>
        /// Methode use for copy all asset created into the output path and the subdirectory
        /// </summary>
        /// <param name="_ovveride">specifie if it's nessecary to override the file in the output path if it's already exist</param>
        /// <param name="subdirectorie">Specifie an subdirectory</param>
        /// <returns>String of error. Null if no error</returns>
        private string CompletBuild(bool _ovveride, string subdirectorie)
        {
            string[] files = null;
            if (Directory.Exists(m_builder.OutputDirectory))
            {
                files = Directory.GetFiles(m_builder.OutputDirectory, "*.xnb", SearchOption.AllDirectories);
                foreach (string elem in files)
                    File.Delete(elem);
                string[] dirs = Directory.GetDirectories(m_builder.OutputDirectory);
                foreach (string elem in dirs)
                    Directory.Delete(elem);
            }
            string error = m_builder.Build();
            if (string.IsNullOrEmpty(error))
            {
                if (!Directory.Exists(m_parameter.OutPut))
                    Directory.CreateDirectory(m_parameter.OutPut);
                files = Directory.GetFiles(m_builder.OutputDirectory, "*.xnb", SearchOption.AllDirectories);
                foreach (string elem in files)
                {
                    string copy = Path.Combine(m_parameter.OutPut, subdirectorie, elem.Replace(m_builder.OutputDirectory, "").Replace(@"\", ""));
                    if (!Directory.Exists(Path.GetDirectoryName(copy)))
                        Directory.CreateDirectory(Path.GetDirectoryName(copy));
                    File.Copy(elem, copy, _ovveride);
                }
                return null;
            }
            return error;
        }
        
        /// <summary>
        /// This method add the reference to the building context
        /// </summary>
        /// <param name="path">Reference path</param>
        public void AddReference(Type _type)
        {
            AddReference(_type.Assembly);
        }

        /// <summary>
        /// This method add the reference to the building context
        /// </summary>
        /// <param name="path">Reference path</param>
        public void AddReference(Assembly _assembly)
        {
            AddReference(_assembly.EscapedCodeBase.Replace("file:///", ""));
        }

        /// <summary>
        /// This method add the reference to the building context
        /// </summary>
        /// <param name="path">Reference path</param>
        public void AddReference(string path)
        {
            m_builder.AddReference(path);
        }

        #endregion
    }
}
